//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// (c) Vladimir Kudinov (boba@nm.ru), 2003
//
// Convert objects' labels.
//

# include "StdAfx.h"
# include "Resource.h"
# include "ConvertLabels.h"
# include "UndoRedo.h"
# include "Map.h"
# include "MapView.h"
# include "Load.h"
# include "Status.h"
# include "Label.h"
# include "Types.h"
# include "Transliteration.h"
# include "Settings.h"
# include "ParseHelpers.h"
# include "PrintUnits.h"
# include "fast_floor.h"
# include "Globals.h"

///////////////////////////////////////////////////////

// This method returns a pointer to the first character of the label text 
// skipping all the special ~[0x...] codes.
static
char * _GetLabelText (string_t & label) {
	size_t index;
	if (label.size () >= 4 && ::_strnicmp (label.c_str (), "~[0x", 4) == 0) {
		if (label.size () > 6 && label [6] == ']')
			index = 7;
		else if (label.size () > 8 && label [8] == ']')
			index = 9;
		else
			index = 0;
	} else
		index = 0;
	return & label [index]; // HACK: we return the pointer to a character in the string's internal array 
}

///////////////////////////////////////////////////////

enum ConvertLabelAction_t {
	claUpperCase     = 0,
	claLowerCase     = 1,
	claCapitalize    = 2,
	claCapitalizeAll = 3,
	claTransliterate = 4,
	claMeters2Feet   = 5,
	claFeet2Meters   = 6
};

// settings' names
const char SETTING_NAME_ACTION         [] = "ConvertLabels_Action";
const char SETTING_NAME_TRANSLITERATOR [] = "ConvertLabels_Transliterator";

struct CConvertLabelsOperationSettings {
	ConvertLabelAction_t action;

	const CTransliterator * pTransliterator;

	CConvertLabelsOperationSettings () :
		action (claUpperCase),
		pTransliterator (NULL)
	{}

	void LoadSettings () {
		action = static_cast<ConvertLabelAction_t> (GetSetting (SETTING_NAME_ACTION, (DWORD) claUpperCase));
		pTransliterator = NULL;
	}

	void SaveSettings () {
		SaveSetting (SETTING_NAME_ACTION, (DWORD) action);
	}
};

class CConvertLabelsAction : public CEditAction {
  public:
	CConvertLabelsAction (const CConvertLabelsOperationSettings & _opSettings) :
		opSettings (_opSettings)
	{}
	virtual void GetName (string_t & _str) const;
	virtual cu_t CanUndo () const {return cuBackup;}
	virtual bool Do ();
	virtual void Undo () {}
	virtual void Redo () {}

  private:
	CConvertLabelsOperationSettings opSettings;

	void ApplyToLabel (string_t & _label, WORD _wCodePage, rgn_t & _rgn);
};

void CConvertLabelsAction::GetName (string_t & _str) const {
	switch (opSettings.action) {
		case claUpperCase:     _str.append ("upper case"); break;
		case claLowerCase:     _str.append ("lower case"); break;
		case claCapitalize:    _str.append ("capitalize"); break;
		case claCapitalizeAll: _str.append ("capitalize all words"); break;
		case claMeters2Feet:   _str.append ("meters to feet"); break;
		case claFeet2Meters:   _str.append ("feet to meters"); break;
		case claTransliterate: _str.append ("transliterate labels"); break;

		default: assert (false);
	}
}

bool CConvertLabelsAction::Do () {
	CCritSec::CLock lock (g_cs);

	const WORD wCodePage = g_map.Header.wCodePage;

	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		rgns_t & rgns = g_map.layers [cKind].rgns;

		for (rgns_t::iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn) {
			rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || rgn.IsWTR ())
				continue;

			string_t & label = rgn.strLabel;
			if (! label.empty ())
				ApplyToLabel (label, wCodePage, rgn);
		}
	}

	return true;
}

static
void _ConvertElevationUnits (float _k, rgn_t & _rgn) {
	if (g_map.pTypeSet->pfIsHeight (_rgn)) {
		float fElevation = 0;
		const char * p;
		if (! ParseReal (_rgn.strLabel.c_str (), _rgn.strLabel.c_str () + _rgn.strLabel.size (), & p, fElevation))
			return;
		fElevation *= _k;

		char strElevation [20];
		PrintElevation (strElevation, NULL, fElevation, g_ElevationUnit, true);

		_rgn.strLabel = strElevation;
	} else if (g_map.pTypeSet->pfIsFeature (_rgn)) {
		const char * pPrefixEnd = ::strstr (_rgn.strLabel.c_str (), "~[0x1f]");
		if (pPrefixEnd == NULL)
			return;
		const char * pElevation = pPrefixEnd + 7;

		float fElevation = 0;
		const char * p;
		if (! ParseReal (pElevation, _rgn.strLabel.c_str () + _rgn.strLabel.size (), & p, fElevation))
			return;
		fElevation *= _k;

		char strElevation [20];
		PrintElevation (strElevation, NULL, fElevation, g_ElevationUnit, true);

		string_t strPrefix (_rgn.strLabel.c_str (), pElevation);
		_rgn.strLabel.swap (strPrefix);
		_rgn.strLabel.append (strElevation);
	}
}

void CConvertLabelsAction::ApplyToLabel (string_t & label, WORD _wCodePage, rgn_t & _rgn) {
	char * labelTextFirstChar = _GetLabelText (label);
	char * labelTextLastChar  = & label [label.length ()];

	switch (opSettings.action) {
		case claUpperCase:
//			LCMapString ?
			::CharUpperBuff (labelTextFirstChar, labelTextLastChar - labelTextFirstChar);
			break;

		case claLowerCase:
			::CharLowerBuff (labelTextFirstChar, labelTextLastChar - labelTextFirstChar);
			break;

		case claCapitalize:	{
			// First, set all the text to lower case.
			::CharLowerBuff (labelTextFirstChar, labelTextLastChar - labelTextFirstChar);

			// Find first letter, skipping punctuation marks, whitespace and digits.
			for (char * pLetter = labelTextFirstChar; pLetter < labelTextLastChar; pLetter = ::CharNextExA (_wCodePage, pLetter, 0)) {
				if (::IsCharAlpha (* pLetter)) {
					::CharUpperBuff (pLetter, 1);
					break;
				}
			}
			break;
		}

		case claCapitalizeAll: {
			// First, set all the text to lower case.
			::CharLowerBuff (labelTextFirstChar, labelTextLastChar - labelTextFirstChar);

			bool bSpace = true;
			for (char * pLetter = labelTextFirstChar; pLetter < labelTextLastChar; pLetter = ::CharNextExA (_wCodePage, pLetter, 0)) {
				if (::IsCharAlphaNumeric (* pLetter)) {
					if (bSpace) {
						::CharUpperBuff (pLetter, 1);
						bSpace = false;
					}
				} else
					bSpace = true;
			}
			break;
		}

		case claMeters2Feet:
			_ConvertElevationUnits (c_feet_per_m,   _rgn);
			break;
		case claFeet2Meters:
			_ConvertElevationUnits (1/c_feet_per_m, _rgn);
			break;

		case claTransliterate: {
			if (opSettings.pTransliterator == NULL)
				break;

			// Copy any symbols prior label text.
			string_t strTransliterated (& label [0], labelTextFirstChar);
			// Transliterate the symbols that consist the label text
			opSettings.pTransliterator->Do (labelTextFirstChar, labelTextLastChar, strTransliterated);

			label.swap (strTransliterated);

			break;
		}

		default: assert (false);
	}
}

///////////////////////////

static
INT_PTR CALLBACK _ConvertLabelsDialogProc (HWND _hWnd, UINT _uMsg, WPARAM _wParam, LPARAM _lParam) {
	switch (_uMsg) {
		case WM_INITDIALOG: {
			// Save the context.
			CConvertLabelsOperationSettings * const pOpSettings = reinterpret_cast<CConvertLabelsOperationSettings *> (_lParam);
			::SetWindowLong (_hWnd, GWL_USERDATA, _lParam);

			// Prepare transliterator.
			g_Transliteration.Load ();
			const size_t cTransliteratorsNum = g_Transliteration.GetNumOfTransliterators ();

			//
			// Prepare action radiobuttons.
			//
			// NOTE: it is assumed that IDs for controls occupy continuous ranges and 
			// are sorted in same order as respective values in enums.
			UINT uiRadioButton = IDC_LABELS_TRANSLITERATE;
			switch (pOpSettings->action) {
				case claUpperCase:     uiRadioButton = IDC_LABELS_TO_UPPER_CASE;    break;
				case claLowerCase:     uiRadioButton = IDC_LABELS_TO_LOWER_CASE;    break;
				case claCapitalize:    uiRadioButton = IDC_LABELS_CAPITALIZE_FIRST; break;
				case claCapitalizeAll: uiRadioButton = IDC_LABELS_CAPITALIZE_ALL;   break;
				case claTransliterate: uiRadioButton = IDC_LABELS_TRANSLITERATE;    break;
				case claMeters2Feet:   uiRadioButton = IDC_LABELS_M2FT;             break;
				case claFeet2Meters:   uiRadioButton = IDC_LABELS_FT2M;             break;
				default: assert (false);
			}
			::CheckRadioButton (_hWnd, uiRadioButton, uiRadioButton, uiRadioButton);
			if (cTransliteratorsNum == 0)
				::EnableWindow (::GetDlgItem (_hWnd, IDC_LABELS_TRANSLITERATE), FALSE);

			// Preapre elevation units translators.
			if ((g_map.pTypeSet->dwFeatures & CTypeSet::tsfHasElevationUnits) == 0) {
				::EnableWindow (::GetDlgItem (_hWnd, IDC_LABELS_M2FT), FALSE);
				::EnableWindow (::GetDlgItem (_hWnd, IDC_LABELS_FT2M), FALSE);
			}

			//
			// Prepare 'Transliterate' field.
			//
			const HWND hTransliteration = ::GetDlgItem (_hWnd, IDC_LABELS_CHOOSE_TRANSLITERATION);

			::EnableWindow (
				::GetDlgItem (_hWnd, IDC_LABELS_CHOOSE_TRANSLITERATION), 
				pOpSettings->action == claTransliterate && cTransliteratorsNum != 0
			);

			for (size_t cTrl = 0; cTrl < cTransliteratorsNum; ++ cTrl) {
				const CTransliterator * const pTransliterator = g_Transliteration.GetTransliterator (cTrl);

				::SendMessage (hTransliteration, CB_INSERTSTRING, cTrl, (LPARAM) pTransliterator->GetName ().c_str ());

				const WORD wLangId = pTransliterator->GetLangId ();
				if (wLangId == ::GetUserDefaultLangID ()) {
					::SendMessage (hTransliteration, CB_SETCURSEL, cTrl, 0);
					pOpSettings->pTransliterator = pTransliterator;
				}
			}

			return TRUE;
		}

		case WM_COMMAND: {
			CConvertLabelsOperationSettings * const pOpSettings = reinterpret_cast<CConvertLabelsOperationSettings *> (::GetWindowLong (_hWnd, GWL_USERDATA));

			const WORD controlID = LOWORD (_wParam);

			// Disable transliteration drop-down list when transliteration is not selected
			// NOTE: it is assumed that controls IDs occupy a continuous range from
			// IDC_LABELS_TO_UPPER_CASE to IDC_LABELS_TRANSLITERATE.
			if (IDC_LABELS_TO_UPPER_CASE <= controlID && controlID <= IDC_LABELS_TRANSLITERATE)
				::EnableWindow (
					::GetDlgItem (_hWnd, IDC_LABELS_CHOOSE_TRANSLITERATION), 
					controlID == IDC_LABELS_TRANSLITERATE
				);

			switch (controlID) {
				// Transliteration selection combo-box.
				case IDC_LABELS_CHOOSE_TRANSLITERATION:
					if (HIWORD (_wParam) == CBN_SELCHANGE) {
						const HWND hTransliteration = ::GetDlgItem (_hWnd, IDC_LABELS_CHOOSE_TRANSLITERATION);

						const size_t cSelIndex = ::SendMessage (hTransliteration, CB_GETCURSEL, 0, 0);
						if (cSelIndex < g_Transliteration.GetNumOfTransliterators ())
							pOpSettings->pTransliterator = g_Transliteration.GetTransliterator (cSelIndex);

						return TRUE;
					}
					break;

				// Action selection radio-buttons.
				case IDC_LABELS_TO_UPPER_CASE:    pOpSettings->action = claUpperCase;     return TRUE;
				case IDC_LABELS_TO_LOWER_CASE:    pOpSettings->action = claLowerCase;     return TRUE;
				case IDC_LABELS_CAPITALIZE_FIRST: pOpSettings->action = claCapitalize;    return TRUE;
				case IDC_LABELS_CAPITALIZE_ALL:   pOpSettings->action = claCapitalizeAll; return TRUE;
				case IDC_LABELS_M2FT:             pOpSettings->action = claMeters2Feet;   return TRUE;
				case IDC_LABELS_FT2M:             pOpSettings->action = claFeet2Meters;   return TRUE;
				case IDC_LABELS_TRANSLITERATE:    pOpSettings->action = claTransliterate; return TRUE;

				case IDOK:     ::EndDialog (_hWnd, 0); return TRUE;
				case IDCANCEL: ::EndDialog (_hWnd, 1); return TRUE;
			}
		}
	}
	return FALSE;
}

void ConvertLabels (HWND _hWnd) {
	CConvertLabelsOperationSettings opSettings;
	// Load from settings stored in registry.
	opSettings.LoadSettings ();

	const INT_PTR nResult = DlgBox (IDD_CONVERT_LABELS, _hWnd, _ConvertLabelsDialogProc, reinterpret_cast<LPARAM> (& opSettings));
	if (nResult == 1)
		// Dialog was canceled.
		return;

	// On OK save choosen settings into the registry.
	opSettings.SaveSettings ();

	PerformEditAction (new CConvertLabelsAction (opSettings));

	Refresh ();
}
